﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading.Tasks;
using LabNet.Server.Domain;

namespace LabNet.Server.Data
{
    public abstract class EntityRepository<T> : IDisposable, IEntityRepository<T> where T : BaseObject
    {
        protected IDataContext Context;
        protected IDatabaseFactory DatabaseFactory;
        protected IDbSet<T> DbSet;
        private bool disposed;

        protected EntityRepository(IDatabaseFactory databaseFactory)
        {
            DatabaseFactory = databaseFactory;
            DbSet = DataContext.DbSet<T>();
            disposed = false;
        }

        public IDataContext DataContext
        {
            get { return Context ?? (Context = DatabaseFactory.Get()); }
        }

        public IQueryable<T> Query
        {
            get { return DbSet; }
        }

        public virtual IQueryable<T> Get()
        {
            var query = Query;
            return query;
        }

        public virtual IList<T> GetList()
        {
            var query = Get().ToList();
            return query;
        }

        public virtual async Task<IList<T>> GetAsync()
        {
            var query = await Get().ToListAsync();
            return query;
        } 

        public virtual T GetById(long id)
        {
            var query = Query.SingleOrDefault(o => o.Id == id);
            return query;
        }

        public virtual T GetById(long? id)
        {
            var query = Query.SingleOrDefault(o => o.Id == id);
            return query;
        }

        public virtual async Task<T> GetByIdAsync(long id)
        {
            var query = await Query.SingleOrDefaultAsync(o => o.Id == id);
            return query;
        }

        public virtual async Task<T> GetByIdAsync(long? id)
        {
            var query = await Query.SingleOrDefaultAsync(o => o.Id == id);
            return query;
        }

        public virtual void InsertOrUpdate(T entity)
        {
            if (entity.Transient())
            {
                Add(entity);
            }
            else
            {
                Update(entity);
            }
        }

        public virtual void Add(T entity)
        {
            var entry = AttachOrDetached(entity);
            entry.State = EntityState.Added;
        }

        public virtual void Update(T entity)
        {
            var entry = AttachOrDetached(entity);
            entry.State = EntityState.Modified;
        }

        public virtual void Remove(T entity)
        {
            var entry = DataContext.Entry(entity);
            if (entry.State != EntityState.Deleted)
            {
                entry.State = EntityState.Deleted;
            }
            else
            {
                DbSet.Attach(entity);
                DbSet.Remove(entity);
            }
        }

        public virtual void Detach(T entity)
        {
            var entry = DataContext.Entry(entity);
            entry.State = EntityState.Detached;
        }

        protected DbEntityEntry AttachOrDetached(T entity)
        {
            var entry = DataContext.Entry(entity);
            if (entry.State == EntityState.Detached)
            {
                DbSet.Attach(entity);
            }

            return entry;
        }



        public virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    DataContext.Dispose();
                }
            }
            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

    }
}
